package org.activequant.data.util.series;

import org.activequant.core.domainmodel.data.Candle;
import org.activequant.core.domainmodel.data.MarketDataEntity;
import org.activequant.core.domainmodel.data.Quote;
import org.activequant.core.domainmodel.data.TradeIndication;
import org.activequant.data.retrieval.ISeriesDataIteratorSource;

/**
 * Transforms market data series into a scaled series by applying linear transformation to all price-values properties.
 * Transformation formula:
 * <pre>
 * newPrice = scale * oldPrice + offset;
 * </pre>
 * The transformation is controlled by {@link #getScale() scale} and {@link #getOffset() offset}
 * properties.
 * <p>
 * ScaledSeriesDataIteratorSource&lt;T extends MarketDataEntity&lt;T&gt;&gt;(extends TransformingDataIteratorSourceBase&lt;T&gt;.<br/>
 * Holds the following associated variables:
 * <ul>
 * <li>scale(double)</li>
 * <li>offset(double)</li>
 * </ul>
 * Holds the following inherited variables:
 * <ul>
 * <li>vendor(String)</li>
 * <li>source(ISeriesDataIteratorSource&lt;T&gt;)</li>
 * </ul>
 * <b>History:</b><br>
 *  - [13.01.2008] Created (Mike Kroutikov)<br>
 *
 *  @author Mike Kroutikov
 */
public class ScaledSeriesDataIteratorSource<T extends MarketDataEntity<T>> extends TransformingDataIteratorSourceBase<T> {
	/**
	 * private double scale = 1.0;
	 */
	private double scale = 1.0;
	/**
	 * private double offset = 0.0;
	 */
	private double offset = 0.0;
	/**
	 * constructs a ScaledSeriesDataIteratorSource&lt;T extends MarketDataEntity&lt;T&gt;&gt;(extends TransformingDataIteratorSourceBase&lt;T&gt;) using the given
	 * vendor(String) and source(ISeriesDataIteratorSource&lt;T&gt;) to set its inherited vendor(String) and source(ISeriesDataIteratorSource&lt;T&gt;)
	 * @param vendor
	 * @param source
	 */
	public ScaledSeriesDataIteratorSource(String vendor, ISeriesDataIteratorSource<T> source) {
		super(vendor, source);
	}
	/**
	 * <strong>1.</strong> If the given item(T) is a Quote, it creates and returns a clone Quote whose bidPrice(double) and askPrice(double) are calculated as: ${originalPrice}*${scale}+${offset}.<br/>
	 * <strong>2.</strong> If the given item(T) is a TradeIndication, it creates and returns a clone TradeIndication whose price(double) is calculated as: ${originalPrice}*${scale}+${offset}.<br/>
	 * <strong>3.</strong> If the given item(T) is a Candle, it creates and returns a clone Candle whose openPrice(double), highPrice(double), lowPrice(double) and closePrice(double) are calculated as: ${originalPrice}*${scale}+${offset}.
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected T transformDataItem(T item) {
		
		if(Quote.class.isAssignableFrom(item.getClass())) {
			Quote q = (Quote) (Object) item;
			
			Quote newItem = q.clone();
			
			double askPrice = newItem.getAskPrice();
			double bidPrice = newItem.getBidPrice();
			
			if(askPrice != Quote.NOT_SET) {
				newItem.setAskPrice(askPrice * scale + offset);
			}
			if(bidPrice != Quote.NOT_SET) {
				newItem.setBidPrice(bidPrice * scale + offset);
			}
			
			return (T) (Object) newItem;
			
		} else if(TradeIndication.class.isAssignableFrom(item.getClass())) {
			TradeIndication t = (TradeIndication) (Object) item;
			
			TradeIndication newItem = t.clone();
			
			double price = newItem.getPrice();
			
			if(price != TradeIndication.NOT_SET) {
				newItem.setPrice(price * scale + offset);
			}

			return (T) (Object) newItem;

		} else if(Candle.class.isAssignableFrom(item.getClass())) {
			Candle c = (Candle) (Object) item;
			
			Candle newItem = c.clone();
			
			double openPrice = newItem.getOpenPrice();
			double closePrice = newItem.getClosePrice();
			double highPrice = newItem.getHighPrice();
			double lowPrice = newItem.getLowPrice();
			
			if(openPrice != Candle.NOT_SET) {
				newItem.setOpenPrice(openPrice * scale + offset);
			}
			if(closePrice != Candle.NOT_SET) {
				newItem.setClosePrice(closePrice * scale + offset);
			}
			if(highPrice != Candle.NOT_SET) {
				newItem.setHighPrice(highPrice * scale + offset);
			}
			if(lowPrice != Candle.NOT_SET) {
				newItem.setLowPrice(lowPrice * scale + offset);
			}
			
			return (T) (Object) newItem;

		} else {
			throw new AssertionError("unexpected data item type: " + item.getClass() + ", " + item);
		}
	}

	/**
	 * Scale of linear transformation.<br/>
	 * returns the associated scale(double)
	 * @return scale.
	 */
	public double getScale() {
		return scale;
	}
	
	/**
	 * Sets scale of linear transformation.<br/>
	 * sets the associated scale(double) with the given val(double)
	 * @param val scale.
	 */
	public void setScale(double val) {
		scale = val;
	}

	/**
	 * Offset of linear transformation.<br/>
	 * returns the associated offset(double)
	 * @return offset.
	 */
	public double getOffset() {
		return offset;
	}
	
	/**
	 * Sets offset of linear transformation.<br/>
	 * sets the associated offset(double) with the given val(double)
	 * @param val offset.
	 */
	public void setOffset(double val) {
		offset = val;
	}
}
